#include "StdAfx.h"
#include "QvDebug.h"
#include "QvMemory.h"
#include "QvSystem.h"
#include "QvString.h"

static int g_isStackWalkerInit = 0;

// The following should be enough for walking the callstack...
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    memset(&c, 0, sizeof(CONTEXT)); \
    c.ContextFlags = contextFlags; \
    __asm    call x \
    __asm x: pop eax \
    __asm    mov c.Eip, eax \
    __asm    mov c.Ebp, ebp \
    __asm    mov c.Esp, esp \
  } while(0);

QvDebug::QvDebug(void)
{
}

QvDebug::~QvDebug(void)
{
}

//////////////////////////////////////////////////////////////////////////
//void QvDebug::PrintDebugString(const QvByte *csFormat, ...)
//{
//    if(csFormat != NULL)
//    {
//        va_list theArgs;
//        va_start(theArgs, csFormat);
//        size_t sizeOfTemplate = _vscprintf(csFormat, theArgs) + 1;
//        QvByte *pBuffer = new QvByte[sizeOfTemplate];
//        memset(pBuffer, 0, sizeOfTemplate);
//        _vsprintf_s_l(pBuffer, sizeOfTemplate, csFormat, NULL, theArgs);
//        va_end(theArgs);
//        QvSystem::OutputDebugStringA(pBuffer);
//        SAFE_DELETE_ARRAY(pBuffer);
//    }
//}

//////////////////////////////////////////////////////////////////////////

void QvDebug::PrintDebugString(const QvChar *wcsFormat, ...)
{
    if(wcsFormat != NULL)
    {
        va_list theArgs;
        va_start(theArgs, wcsFormat);
        //size_t sizeOfTemplate = _vscwprintf(wcsFormat, theArgs) + 1;
        //QvChar *pBuffer = new QvChar[sizeOfTemplate];
        //wmemset(pBuffer, 0, sizeOfTemplate);
        //_vswprintf_s_l(pBuffer, sizeOfTemplate, wcsFormat, NULL, theArgs);
        //va_end(theArgs);
        //QvSystem::OutputDebugStringW(pBuffer);
        //SAFE_DELETE_ARRAY(pBuffer);
        QvString strInfo;
        strInfo.AppendFormatArgs(wcsFormat, theArgs);
        QvSystem::OutputDebugStringW(strInfo.GetUnicodeBuffer());
        va_end(theArgs);
    }
}

#define nSymPathLen 4096

void QvDebug::InitStackWalker(const QvByte *szUserSymPath)
{
    g_isStackWalkerInit = 1;
    // Build the sym-path:
    char szSymPath[nSymPathLen] = {0};
    //const size_t nSymPathLen = 4096;
    // Now first add the (optional) provided sympath:
    if (szUserSymPath != NULL)
    {
        strcat_s(szSymPath, nSymPathLen, szUserSymPath);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    strcat_s(szSymPath, nSymPathLen, ".;");
    //
    const size_t nTempLen = 1024;
    char szTemp[nTempLen];
    // Now add the current directory:
    if (GetCurrentDirectoryA(nTempLen, szTemp) > 0)
    {
        szTemp[nTempLen-1] = 0;
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    // Now add the path for the main-module:
    if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        ::PathRemoveFileSpecA(szTemp);
        if (strlen(szTemp) > 0)
        {
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
        }
    }
    if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
        // also add the "system32"-directory:
        strcat_s(szTemp, nTempLen, "\\system32");
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        // add the "symbols"-directory:
        strcat_s(szTemp, nTempLen, "\\symbols");
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
        // also add the "dll"-directory:
        strcat_s(szTemp, nTempLen, "\\dll");
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, ";");
    }
    if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0)
    {
        szTemp[nTempLen-1] = 0;
        strcat_s(szSymPath, nSymPathLen, "SRV*");
        strcat_s(szSymPath, nSymPathLen, szTemp);
        strcat_s(szSymPath, nSymPathLen, "\\websymbols");
        strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;");
    }
    else
    {
        strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;");
    }
    HANDLE hProcess = GetCurrentProcess();
    // SymInitialize
    SymInitialize(hProcess, szSymPath, FALSE);
    // SymGetOptions
    DWORD symOptions = SymGetOptions();
    symOptions |= SYMOPT_LOAD_LINES;
    symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
    symOptions |= SYMOPT_NO_PROMPTS;
    // SymSetOptions
    symOptions = SymSetOptions(symOptions);
    // SymGetSearchPath
    //char buf[4096] = {0};
    //SymGetSearchPath(hProcess, buf, 1024);
    HMODULE hMods[1024];
    DWORD cbNeeded = 0;
    MODULEINFO mi;
    if(EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
    {
        char szFileName[MAX_PATH];
        char szBaseName[MAX_PATH];
        for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++ )
        {
            szFileName[0] = 0;
            szBaseName[0] = 0;
            // base address, size
            GetModuleInformation(hProcess, hMods[i], &mi, sizeof(MODULEINFO));
            // image file name
            GetModuleFileNameExA(hProcess, hMods[i], szFileName, MAX_PATH);
            // module name
            GetModuleBaseNameA(hProcess, hMods[i], szBaseName, MAX_PATH);
            SymLoadModule(hProcess, 0, szFileName, szBaseName, (DWORD) mi.lpBaseOfDll, mi.SizeOfImage);
        }
    }
}

//
void QvDebug::ShowStackTrace()
{
    QvString str;
    str.AppendFormat(L"\r\n==============StackTrace Start==============\r\n");
    QvDebug::GetStackTrace(str);
    str.AppendFormat(L"============== StackTrace End ==============\r\n");
    QvDebug::PrintDebugString(str.GetUnicodeBuffer());
}

void QvDebug::GetStackTrace(QvString& str, CONTEXT *pContext)
{
    if(g_isStackWalkerInit == 0)
    {
        InitStackWalker();
        g_isStackWalkerInit = 1;
    }
    CONTEXT theContext;
    if(pContext == NULL)
    {
        GET_CURRENT_CONTEXT(theContext, CONTEXT_FULL);
        pContext = &theContext;
    }
    STACKFRAME sf;
    memset( &sf, 0, sizeof( STACKFRAME ) );

    sf.AddrPC.Offset = pContext->Eip;
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrStack.Offset = pContext->Esp;
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Offset = pContext->Ebp;
    sf.AddrFrame.Mode = AddrModeFlat;

    DWORD machineType = IMAGE_FILE_MACHINE_I386;

    HANDLE hProcess = GetCurrentProcess();
    HANDLE hThread = GetCurrentThread();

    for( ; ; )
    {
        if( !StackWalk(machineType, hProcess, hThread, &sf, pContext, 0, 
                            SymFunctionTableAccess, SymGetModuleBase, 0 ) )
        {
            break;
        }
        if( sf.AddrFrame.Offset == 0 )
        {
            break;
        }
        BYTE symbolBuffer[ sizeof( IMAGEHLP_SYMBOL ) + 1024 ];
        PIMAGEHLP_SYMBOL pSymbol = ( PIMAGEHLP_SYMBOL ) symbolBuffer;

        pSymbol->SizeOfStruct = sizeof( symbolBuffer );
        pSymbol->MaxNameLength = 1024;

        DWORD symDisplacement = 0;
        char* pFunName = NULL;
        if( SymGetSymFromAddr( hProcess, sf.AddrPC.Offset, &symDisplacement, pSymbol ) )
        {
            pFunName = pSymbol->Name;
            if(::strcmp("QvDebug::GetStackTrace", pFunName) == 0)
            {
                continue;
            }
            if(::strcmp("QvDebug::ShowStackTrace", pFunName) == 0)
            {
                continue;
            }
        }
        IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
        DWORD dwLineDisplacement;
        if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
        {
            if(pFunName)
            {
                str.AppendFormat(L"> %s : %s (%d)\r\n", lineInfo.FileName, pFunName, lineInfo.LineNumber);
            }
            else
            {
                str.AppendFormat(L"> %s : (%d)\r\n", lineInfo.FileName, lineInfo.LineNumber);
            }
        }
        else if(pFunName)
        {
            str.AppendFormat(L"> %s (?)\r\n", pFunName);
        }
    }
}

